package gov.fnal.xpath2ldap;

import java.util.List;
import java.util.Stack;
import java.util.LinkedHashMap;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.FileOutputStream;

import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import javax.xml.xpath.XPath;

import org.w3c.dom.*;

import org.xml.sax.InputSource;
import org.apache.commons.cli.CommandLine;

/**
 * User: tdh
 * Date: Dec 15, 2008
 * Time: 10:10:07 AM
 */

public class XPathLDAPEvaluator {
        XMLAdapter xmler;
        LPathSearch searcher;
        DSMLTransform xformer;
        boolean output_ldap_dn;
        boolean output_all_ldap_attributes;
        boolean evaluate_expression=false;
        boolean write_source_xml=true;
        public static final String LF = "\n";
        public static final String DN = "dn";

    public XPathLDAPEvaluator(CommandLine cmdline) {
        xmler = new XMLAdapter(cmdline);
        searcher = new LPathSearch(xmler);
        xformer = new DSMLTransform(xmler);
        output_ldap_dn = xmler.getCommandLine().hasOption(XPathSearchOptions.OUTPUT_LDAP_DN);
        output_all_ldap_attributes = xmler.getCommandLine().hasOption(XPathSearchOptions.OUTPUT_ALL_LDAP_ATTRIBUTES);
        String output = xmler.getCommandLine().getOptionValue(XPathSearchOptions.OUTPUT, XPathSearchOptions.OUTPUT_DEFAULT);
        write_source_xml = XPathSearchOptions.OUTPUT_SOURCE_XML.equals(output);
        String action = xmler.getCommandLine().getOptionValue(XPathSearchOptions.ACTION, XPathSearchOptions.ACTION_DEFAULT);
        evaluate_expression = XPathSearchOptions.ACTION_EVALUATE.equals(action);
    }

    public XPathLDAPEvaluator(String hostname, int port) {
        xmler = new XMLAdapter(hostname, port);
        searcher = new LPathSearch(xmler);
        xformer = new DSMLTransform(xmler);
    }

    public String search(String expr) {

        System.err.println(expr);
        Document fromldap=null;

        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();


        boolean from_file=false;
        boolean write_file=false;
        if(!from_file)
            try {

                XqueryxStaxParser runparser = new XqueryxStaxParser();

                String xqueryx_str = runparser.parseXpathToXqueryX(expr);
                List<String> lpathList = runparser.parseXqueryX(xqueryx_str);
                List<Pair<String, Boolean>> combined = runparser.combineOperations(lpathList);
                List<String> boolcomb = runparser.combineBooleans(combined);
                //StringBuilder ldap_expression = new StringBuilder();
                //for(String entry : lpathList) {
                //    ldap_expression.append(entry);
                //}
                //System.out.println("ldap expression is " + ldap_expression);
                List<String> lFullPaths = runparser.getFullPaths(boolcomb);
                //System.out.println("Search paths are ");
                //for(String fullpath : lFullPaths) {
                //    System.out.println(fullpath);
                //}

                //searcher.combineCommonAncestors(ldap_expression.toString());
                Pair<Document, LinkedHashMap<String, Stack<String>>> fromLPathSearch;
                fromLPathSearch = searcher.search(lFullPaths);
                //fromldap = fromLPathSearch.getFirst();
                fromldap = xformer.xformDSMLToStructuredXML(fromLPathSearch);
                if(write_file) {
                    try {
                    	String tmpdir = System.getProperty("xpathsearch.home", System.getProperty("user.home")) + "/tmp";
                        FileOutputStream fos = new FileOutputStream(tmpdir + "/fromsearch.xml");
                        XMLAdapter.serialize(fromldap, fos);
                        fos.flush();
                        fos.close();
                    } catch (Exception e) {
                        System.err.println("Could not serialize result " + e);
                    }
                }
                if(write_source_xml) {
                    try {
                        XMLAdapter.serialize(fromldap, System.out);
                    } catch (Exception e) {
                        System.err.println("Could not serialize result " + e);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        int num_results=-1;
        StringBuilder response = new StringBuilder();
        if(evaluate_expression)
            try {

                javax.xml.xpath.XPathExpression expression;

                // 3. Compile an XPath string into an XPathExpression
                //javax.xml.xpath.XPathExpression expression = xpath.compile("//StorageElement/[@UniqueID=srm.unl.edu][@Name|@Status|@TotalOnlineSize|@UsedOnlineSize]");

                //expression = xpath.compile("/dsml/directory-entries/entry[contains(@dn,'grid')]/attr[@name='GlueSEUniqueID']/value[text()='sigmorgh.hpcc.ttu.edu']/text()");
                //javax.xml.xpath.XPathExpression expression = xpath.compile("/dsml/directory-entries/entry");
                //javax.xml.xpath.XPathExpression expression = xpath.compile("/XMLTooling/ObjectProviders/ObjectProvider/BuilderClass");
                //javax.xml.xpath.XPathExpression expression = xpath.compile("/dsml");
                //javax.xml.xpath.XPathExpression expression = xpath.compile("/bookstore");
                ///value=srm.unl.edu]/sibling::GlueSEName|sibling::GlueSEStatus|sibling::GlueSETotalOnlineSize:|sibling::GlueSEUsedOnlineSize:/value"); //[@dn=*,o=grid]

                // 4. Evaluate the XPath expression on an input document
                //String result = expression.evaluate(new org.xml.sax.InputSource("/home/tdh/util/schematools-0.0.1/output/glue1_3_example.xml"));
                //String result = expression.evaluate(new org.xml.sax.InputSource("/home/tdh/util/schematools-0.0.1/output/bdii-t.xml"));
                //String result = expression.evaluate(new org.xml.sax.InputSource("/home/tdh/tmp/xacml20-context-config-osg.xml"));
                //String result = expression.evaluate(new org.xml.sax.InputSource("/home/tdh/util/schematools-0.0.1/dsmltools"));
                //Object result = expression.evaluate(new org.xml.sax.InputSource("/home/tdh/tmp/bdii-t.xml"), XPathConstants.NODESET);
                //Object result = expression.evaluate(new org.xml.sax.InputSource("/home/tdh/util/schematools-0.0.1/dsmltools/bdii-iu-1.xml"), XPathConstants.NODESET);
                //expression = xpath.compile("/dsml/directory-entries/entry[contains(@dn,'grid')]/attr[@name='GlueVOInfoAccessControlBaseRule']/value[text()='ilc']/text()/../../../@dn");                                                                                                           /
                //expression = xpath.compile("/Entries/*[contains(@dn,concat('GlueSEUniqueID',substring-after(//Entries/GlueVOInfo[contains(@dn,'o=grid')]/GlueVOInfoAccessControlBaseRule/value[text()='ilc']/../../@dn,'GlueSEUniqueID')))]");
                //expression = xpath.compile("/Entries/*[contains(@dn,concat('GlueSEUniqueID',substring-after(//Entries/*[contains(@dn,concat('GlueSEUniqueID',substring-after(//Entries/GlueVOInfo[contains(@dn,'o=grid')]/GlueVOInfoAccessControlBaseRule/value[text()='ilc']/../../@dn,'GlueSEUniqueID')))]/GlueSEAccessProtocolType/value[text()='gsiftp']/../../@dn,'GlueSEUniqueID')))]/GlueSEControlProtocolLocalID/value[text()='srm_v2']/../..");
                //expression = xpath.compile("//GlueSE[GlueSEAccessProtocol[@GlueSEAccessProtocolType='gsiftp'] and GlueSEControlProtocol[@GlueSEControlProtocolType='SRM'][@GlueSEControlProtocolVersion='2.2.0']]/GlueSA/GlueVOInfo[@GlueVOInfoAccessControlBaseRule='ilc']/../../GlueSEControlProtocol[@GlueSEControlProtocolVersion='2.2.0']/@GlueSEControlProtocolEndpoint");
                //expression = xpath.compile("//GlueSE[GlueSEAccessProtocol]/GlueSA[@GlueSAFreeOnlineSize>0]/GlueVOInfo[@GlueVOInfoAccessControlBaseRule='ilc']/../../GlueSEAccessProtocol/@GlueSEAccessProtocolType");
                //expression = xpath.compile("//GlueSE[GlueSEControlProtocol]/GlueSA[@GlueSATotalOnlineSize>=0 or @GlueSAStateAvailableSpace>0]/GlueVOInfo[@GlueVOInfoAccessControlBaseRule='ilc']/../../@GlueSEImplementationName");
                expression = xpath.compile(expr);
                if(from_file) {
                    InputSource source = new InputSource("/home/tdh/tmp/fromsearch-5.xml");
                    DocumentBuilderFactory dbf = null;
                    DocumentBuilder db = null;
                    try {
                        if ( dbf == null ) {
                            dbf = DocumentBuilderFactory.newInstance();
                            dbf.setNamespaceAware( true );
                            dbf.setValidating( false );
                        }
                    } catch ( Exception e ) {
                        e.printStackTrace();
                    }
                    db = dbf.newDocumentBuilder();
                    fromldap = db.parse( source );
                }

                Object result = expression.evaluate(fromldap, XPathConstants.NODESET);

                NodeList nodes = (NodeList) result;
                num_results = nodes.getLength();
                System.err.println(num_results);
                System.err.println();

                for(int i=0; i<nodes.getLength(); i++) {
                    Node node = nodes.item(i);
                    if(node instanceof Text) {
                        response.append(node.getTextContent()).append(LF);
                    } else if(node instanceof Attr) {
                        if (output_ldap_dn && !DN.equals(node.getNodeName())) {
                             String dn = ((Attr)node).getOwnerElement().getAttribute(DN);
                             if(dn!=null) {
                                 response.append(dn).append(LF);
                             }
                        }
                        response.append(node.getNodeValue()).append(LF);
                    } else {
                        NamedNodeMap attrs = node.getAttributes();
                        if(attrs.getLength()==0) {
                            response.append(node.getNodeName()).append(LF);
                        } else {
                            for(int j=0; j<attrs.getLength(); j++) {
                                Node attr = attrs.item(j);
                                if (attr instanceof Attr) {
                                    if ( output_all_ldap_attributes ||
                                        (output_ldap_dn && DN.equals(attr.getNodeName())) ) {
                                        response.append(attr.getNodeValue()).append(LF);
                                    }
                                }
                            }
                        }
                    }


                }


            } catch (Exception e) {
                e.printStackTrace();
            }

        if(num_results<1) {
            response.append(num_results).append("\n");
        }
        
        return response.toString();
    }

    /*public static void writeXmlToFile(java.io.File f, Document d) {
            Transformer transformer =
                TransformerFactory.newInstance().newTransformer();
            java.io.FileOutputStream out = new java.io.FileOutputStream(f);
            StreamResult result = new StreamResult(out);
            DOMSource source = new DOMSource(d);
            transformer.setOutputProperty(OutputKeys.INDENT,"yes");
            //depending on which library you have
            transformer.setOutputProperty("{http://xml.apache.org/xalan%7dindent-amount","4");
            transformer.setOutputProperty("{http://xml.apache.org/xslt%7dindent-amount","4");
            transformer.setOutputProperty(OutputKeys.METHOD,"xml");
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
            transformer.transform(source, result);
    }  */
}